package com.jinbiao.quasar.scheduling.quartz;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.jinbiao.quasar.scheduling.constant.ScheduleConstants;
import com.jinbiao.quasar.scheduling.exception.TaskException;
import org.noear.solon.Solon;
import org.noear.solon.Utils;
import org.noear.solon.core.Lifecycle;
import com.jinbiao.quasar.scheduling.quartz.QuartzProxy;
import com.jinbiao.quasar.scheduling.scheduled.JobHolder;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.spi.MutableTrigger;

import java.math.BigDecimal;
import java.time.ZoneId;
import java.util.Date;
import java.util.TimeZone;

/**
 * Quartz 任务调度代理
 *
 * @author noear
 * @since 2.2
 */
public class QuartzSchedulerProxy implements Lifecycle {
    Scheduler _scheduler = null;

    @Override
    public void start() throws Throwable {
        tryInitScheduler();
        _scheduler.start();
    }

    @Override
    public void stop() throws Throwable {
        if (_scheduler != null) {
            _scheduler.shutdown();

            _scheduler = null;
        }
    }

    private JobKey getJobKey(String name) {
        String jobGroup = Utils.annoAlias(Solon.cfg().appName(), "solon");
        return JobKey.jobKey(name, jobGroup);
    }

    private JobKey getJobKey(String name,String group) {
        return JobKey.jobKey(name, group);
    }

    public boolean exists(String name) throws SchedulerException {
        if (_scheduler != null) {
            return _scheduler.checkExists(getJobKey(name));
        } else {
            return false;
        }
    }

    /**
     * 移除 job
     */
    public void remove(String name) throws SchedulerException {
        if (_scheduler != null) {
            _scheduler.resumeJob(getJobKey(name));
        }
    }

    public void pause(String name) throws SchedulerException {
        if (_scheduler != null) {
            _scheduler.pauseJob(getJobKey(name));
        }
    }

    public void resume(String name) throws SchedulerException {
        if (_scheduler != null) {
            _scheduler.resumeJob(getJobKey(name));
        }
    }

    public void triggerJob(String name) throws SchedulerException{
        if (_scheduler != null) {
            _scheduler.triggerJob(getJobKey(name));
        }
    }


    /**
     * 注册 job（on start）
     */
    public void register(JobHolder jobHolder) throws SchedulerException, TaskException {
        tryInitScheduler();
        //String jobGroup = Utils.annoAlias(Solon.cfg().appName(), "solon");

        if (Utils.isEmpty(jobHolder.getScheduled().cron())) {
            regJobByFixedRate(jobHolder);
        } else {
            regJobByCron(jobHolder);
        }
    }

    private void regJobByCron(JobHolder jobHolder/*, String cron, String zone, String jobGroup*/) throws SchedulerException, TaskException {
        tryInitScheduler();
        if(StrUtil.isNotEmpty(jobHolder.getScheduled().startTime())) {
            JobDetail jobDetail = JobBuilder.newJob("0".equals(jobHolder.getScheduled().concurrent()) ? QuartzProxy.class : ConcurrentQuartzProxy.class)
                    .withIdentity(jobHolder.getName(), jobHolder.getScheduled().jobGroup())
                    .build();
            Date startTime = DateUtil.parse(jobHolder.getScheduled().startTime(), DatePattern.NORM_DATETIME_PATTERN);
            long delayCount = jobHolder.getScheduled().initialDelay();
            startTime = DateUtil.offsetSecond(startTime, new BigDecimal(delayCount).intValue());
            if (_scheduler.checkExists(jobDetail.getKey()) == false) {
                CronScheduleBuilder builder = CronScheduleBuilder.cronSchedule(jobHolder.getScheduled().cron());
                builder = handlerCronScheduleMisfirePolicy(jobHolder, builder);
                //支持时区配置
                Date endTime = null;
                if (Utils.isNotEmpty(jobHolder.getScheduled().zone())) {
                    builder.inTimeZone(TimeZone.getTimeZone(ZoneId.of(jobHolder.getScheduled().zone())));
                }
                if (StrUtil.isNotEmpty(jobHolder.getScheduled().endTime())) {
                    endTime = DateUtil.parse(jobHolder.getScheduled().endTime(), DatePattern.NORM_DATETIME_PATTERN);
                }
                if (endTime == null) {
                    Trigger trigger = TriggerBuilder.newTrigger()
                            .withIdentity(jobHolder.getName(), jobHolder.getScheduled().jobGroup())
                            .startAt(startTime)
                            .withSchedule(builder)
                            .build();
                    _scheduler.scheduleJob(jobDetail, trigger);
                }else{
                    Trigger trigger = TriggerBuilder.newTrigger()
                            .withIdentity(jobHolder.getName(), jobHolder.getScheduled().jobGroup())
                            .startAt(startTime)
                            .withSchedule(builder)
                            .endAt(endTime)
                            .build();
                    _scheduler.scheduleJob(jobDetail, trigger);
                }
            }
        }
    }

    private void regJobByFixedRate(JobHolder jobHolder/*, long milliseconds, String jobGroup*/) throws SchedulerException, TaskException {
        tryInitScheduler();
        if(StrUtil.isNotEmpty(jobHolder.getScheduled().startTime())) {
            JobDetail jobDetail = JobBuilder.newJob("0".equals(jobHolder.getScheduled().concurrent()) && jobHolder.getScheduled().fixedDelay() == 0l ? QuartzProxy.class : ConcurrentQuartzProxy.class)
                    .withIdentity(jobHolder.getName(), jobHolder.getScheduled().jobGroup())
                    .build();
            Date startTime = DateUtil.parse(jobHolder.getScheduled().startTime(), DatePattern.NORM_DATETIME_PATTERN);
            long delayCount = jobHolder.getScheduled().initialDelay();
            startTime = DateUtil.offsetSecond(startTime, new BigDecimal(delayCount).intValue());
            Date endTime = null;
            if (StrUtil.isNotEmpty(jobHolder.getScheduled().endTime())) {
                endTime = DateUtil.parse(jobHolder.getScheduled().endTime(), DatePattern.NORM_DATETIME_PATTERN);
            }
            if (_scheduler.checkExists(jobDetail.getKey()) == false) {
                SimpleScheduleBuilder builder = SimpleScheduleBuilder.simpleSchedule();
                long fixedRate = jobHolder.getScheduled().fixedRate();
                if (jobHolder.getScheduled().fixedDelay() != 0) {
                    fixedRate = fixedRate + jobHolder.getScheduled().fixedDelay();
                }
                builder.withIntervalInMilliseconds(fixedRate);
                builder.repeatForever();
                builder = handlerSimpleScheduleMisfirePolicy(jobHolder, builder);
                if (endTime == null) {
                    Trigger trigger = TriggerBuilder.newTrigger()
                            .withIdentity(jobHolder.getName(), jobHolder.getScheduled().jobGroup())
                            .startAt(startTime)
                            .withSchedule(builder)
                            .build();
                    _scheduler.scheduleJob(jobDetail, trigger);
                } else {
                    Trigger trigger = TriggerBuilder.newTrigger()
                            .withIdentity(jobHolder.getName(), jobHolder.getScheduled().jobGroup())
                            .startAt(startTime)
                            .withSchedule(builder)
                            .endAt(endTime)
                            .build();
                    _scheduler.scheduleJob(jobDetail, trigger);
                }

            }
        }
    }

    public static SimpleScheduleBuilder handlerSimpleScheduleMisfirePolicy(JobHolder jobHolder,SimpleScheduleBuilder cb) throws TaskException {
        switch (jobHolder.getScheduled().misfirePolicy())
        {
            case ScheduleConstants.MISFIRE_DEFAULT:
                return cb;
            case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
                return cb.withMisfireHandlingInstructionFireNow();
            case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
                return cb.withMisfireHandlingInstructionNowWithExistingCount();
            case ScheduleConstants.MISFIRE_DO_NOTHING:
                return cb.withMisfireHandlingInstructionNextWithRemainingCount();
            default:
                throw new TaskException("The task misfire policy '" + jobHolder.getScheduled().misfirePolicy()
                        + "' cannot be used in cron schedule tasks", TaskException.Code.CONFIG_ERROR);
        }
    }
    public static CronScheduleBuilder handlerCronScheduleMisfirePolicy(JobHolder jobHolder,CronScheduleBuilder cb) throws TaskException {
        switch (jobHolder.getScheduled().misfirePolicy())
        {
            case ScheduleConstants.MISFIRE_DEFAULT:
                return cb;
            case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
                return cb.withMisfireHandlingInstructionIgnoreMisfires();
            case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
                return cb.withMisfireHandlingInstructionFireAndProceed();
            case ScheduleConstants.MISFIRE_DO_NOTHING:
                return cb.withMisfireHandlingInstructionDoNothing();
            default:
                throw new TaskException("The task misfire policy '" + jobHolder.getScheduled().misfirePolicy()
                        + "' cannot be used in cron schedule tasks", TaskException.Code.CONFIG_ERROR);
        }
    }

    public void setScheduler(Scheduler scheduler) {
        //如果已存在，则不可替换
        if (_scheduler == null) {
            _scheduler = scheduler;
        }
    }

    private void tryInitScheduler() throws SchedulerException {
        if (_scheduler == null) {
            synchronized (this) {
                if (_scheduler == null) {
                    //默认使用：直接本地调用
                    SchedulerFactory schedulerFactory = new StdSchedulerFactory();
                    _scheduler = schedulerFactory.getScheduler();
                    _scheduler.clear();
                }
            }
        }
    }

    public boolean exists(String name, String jobGroup) throws SchedulerException {
        if (_scheduler != null) {
            return _scheduler.checkExists(getJobKey(name,jobGroup));
        } else {
            return false;
        }
    }

    public void resume(String name, String jobGroup) throws SchedulerException {
        if (_scheduler != null) {
            _scheduler.resumeJob(getJobKey(name,jobGroup));
        }
    }

    public void pause(String name, String jobGroup) throws SchedulerException {
        if (_scheduler != null) {
            _scheduler.pauseJob(getJobKey(name,jobGroup));
        }
    }

    public void remove(String name, String jobGroup) throws SchedulerException {
        if (_scheduler != null) {
            _scheduler.deleteJob(getJobKey(name,jobGroup));
        }
    }

    public void triggerJob(String name, String jobGroup) throws SchedulerException, InstantiationException, IllegalAccessException {
        if (_scheduler != null) {
            JobDetail jobDetail = JobBuilder.newJob(ConcurrentQuartzProxy.class)
                    .withIdentity(name, jobGroup+"oneTime")
                    .build();
            SimpleScheduleBuilder builder = SimpleScheduleBuilder.simpleSchedule();
            builder.withRepeatCount(1);
            builder.withIntervalInMilliseconds(50l);
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(name, jobGroup+"oneTime")
                    .startNow()
                    .withSchedule(builder)
                    .build();
            _scheduler.scheduleJob(jobDetail, trigger);
        }
    }
}
